﻿/********************************************************************************
* Copyright 2010 Zane Thorn (zane.thorn@gmail.com)                              *
*                                                                               *
* NeturalMath is free software: you can redistribute it and/or modify           *
* it under the terms of the GNU Lesser General Public License as published by   *
* the Free Software Foundation, either version 3 of the License, or             *
* (at your option) any later version.                                           *
*                                                                               *
* NeturalMath is distributed in the hope that it will be useful,                *
* but WITHOUT ANY WARRANTY; without even the implied warranty of                *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 *
* GNU Lesser General Public License for more details.                           *
*                                                                               *
* You should have received a copy of the GNU Lesser General Public License      *
* along with NeturalMath.  If not, see <http://www.gnu.org/licenses/>.          *
********************************************************************************/

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using NeturalMath.Expressions;
using NeturalMath.Language;
using NeturalMath.Units;

namespace NeturalMath
{
    public class MathRuntime
    {
        #region Constants

        

        #endregion

        #region Local Members

        #endregion

        #region Constructors

        public MathRuntime()
        {
            StdOut = Console.Out;
            StdIn = Console.In;
            StdError = Console.Error;

            Measurements = new Measurements(this);
            SystemFunctions = new SystemFunctions(this);

            ResetRoot();
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the currently executing domain
        /// </summary>
        public Domain CurrentDomain { get; private set; }

        /// <summary>
        /// Gets the root domain for a NeturalMath application
        /// </summary>
        public Domain RootDomain { get; private set; }

        public BlockSymbol CurrentScope { get; private set; }

        public TextWriter StdOut { get; private set; }
        public TextWriter StdError { get; private set; }
        public TextReader StdIn { get; private set; }

        public Measurements Measurements { get; private set; }
        public SystemFunctions SystemFunctions { get; private set; }

        #region System Constants

        /// <summary>
        /// NeturalMath constant for an empty string ""
        /// </summary>
        
        public  MathValue EmptyString { get; private set;}

        /// <summary>
        /// NeturalMath constant for a string representing 'true'
        /// </summary>
        
        public  MathValue TrueString { get; private set; }

        /// <summary>
        /// NeturalMath constant for an string representing 'false'
        /// </summary>
        
        public  MathValue FalseString { get; private set; }

        /// <summary>
        /// NeturalMath constant for an string representing 'undefined'
        /// </summary>
        
        public  MathValue UndefinedString { get; private set; }

        /// <summary>
        /// NeturalMath constant for an string representing 'infinity'
        /// </summary>
        
        public  MathValue InfinityString { get; private set; }

        /// <summary>
        /// NeturalMath constant for an string representing '-infinity'
        /// </summary>
        
        public  MathValue NegativeInfinityString { get; private set;}

        /// <summary>
        /// NeturalMath constant for void
        /// </summary>
        
        public  MathValue Void { get; private set; }

        /// <summary>
        /// NeturalMath constant for true
        /// </summary>
        
        public  MathValue True { get; private set; }

        /// <summary>
        /// NeturalMath constant for false
        /// </summary>
        
        public  MathValue False { get; private set; }

        /// <summary>
        /// NeturalMath constant for infinity
        /// </summary>
        
        public  MathValue Infinity { get; private set; }

        /// <summary>
        /// NeturalMath constant for negative infinity
        /// </summary>
        
        public  MathValue NegativeInfinity { get; private set;}

        /// <summary>
        /// NeturalMath constant for the maximum allowed number
        /// </summary>
        
        public  MathValue MaxNumber { get; private set;}

        /// <summary>
        /// NeturalMath constant for the minimum allowed number
        /// </summary>
        
        public  MathValue MinNumber { get; private set;}

        /// <summary>
        /// NeturalMath constant for an infinitly small value
        /// </summary>
        
        public  MathValue Infintismal { get; private set;}

        /// <summary>
        /// NeturalMath constant for an undefined number (divide by zero)
        /// </summary>
        
        public  MathValue Undefined { get; private set;}

        /// <summary>
        /// NeturalMath constant for zero
        /// </summary>
        
        public  MathValue Zero { get; private set;}

        /// <summary>
        /// NeturalMath constant for one
        /// </summary>
        
        public  MathValue One { get; private set;}

        /// <summary>
        /// NeturalMath constant for a complex with a real of zero and an i of 1
        /// </summary>
        
        public  MathValue I { get; private set;}

        /// <summary>
        /// NeturalMath constant for an empty set
        /// </summary>
        public  MathValue EmptySet { get; private set;}

        public  MathValue PI { get; private set; }

        public  MathValue E { get; private set; }

        #endregion

        #endregion

        #region Public Methods

        #region Execution Methods

        /// <summary>
        /// Interprets an arbitrary block of text and returns a set of expressions for each line
        /// </summary>
        /// <param name="raw"></param>
        /// <returns></returns>
        public MathExpression InterpretText(string raw)
        {
            var reader = new StringReader(raw);
            return InterpretText(reader);
        }

        /// <summary>
        /// Interprets a file (based on file path) and returns a set of expressions for each line
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public MathExpression InterpretFile(string path)
        {
            var reader = new StreamReader(path);
            return InterpretText(reader);
        }

        public MathExpression InterpretText(TextReader reader)
        {
            var parser = new Parser(reader, this);
            var expressions= parser.GetAllExpressions().ToArray();
            if (expressions.Length == 1)
                return expressions[0];
            return new DomainExpression(expressions, this);
        }

        public string AnalyzeText(string raw)
        {
            var visitor = new DefVisitor(this);
            return visitor.Visit(InterpretText(raw));
        }

        public MathValue ExecuteFile(string path)
        {
            return ExecuteText(new StreamReader(path));
        }

        public MathValue ExecuteText(string raw)
        {
            return ExecuteText(new StringReader(raw));
        }

        public MathValue ExecuteText(TextReader reader)
        {
            var expression = InterpretText(reader);
            return expression.Execute();
        }

        //public Func<MathValue> CompileFileToLambda(string path)
        //{
        //    var reader = new StreamReader(path);
        //    return CompileTextToLambda(reader);
        //}

        //public Func<MathValue> CompileTextToLambda(string raw)
        //{
        //    return CompileTextToLambda(new StringReader(raw));
        //}

        //public Func<MathValue> CompileTextToLambda(TextReader reader)
        //{
        //    var expressions = InterpretText(reader);
        //    return CompileExpressionToLambda(expressions);
        //}

        //public Func<MathValue> CompileExpressionToLambda(MathExpression expression)
        //{
        //    var exp = _functionalVisitor.Visit(expression);
        //    return exp;
        //}

        //public Expression<Func<MathValue>> CompileToExpression(TextReader reader)
        //{
        //    var func = CompileTextToLambda(reader);
        //    return func.ToExpression();
        //}

        public void Import (string path,CompilerSettings options)
        {
            ExecuteFile(path);
        }

        public void Import(MathValue path)
        {
            var p = (string)path.ConvertToString().Value;
            ExecuteFile(p);
        }

        #endregion

        #region IL Compiling Methods

        //protected void CompileToIL(TextReader reader, MethodBuilder builder)
        //{
        //    var exp = CompileToExpression(reader);
        //    exp.CompileToMethod(builder);
        //}

        #endregion

        #region Std IO methods

        public void Print(params MathValue[] values)
        {
            foreach (var v in values)
            {
                StdOut.WriteLine(v.GetValue());
            }
        }

        public void Def(params MathExpression[] expressions)
        {
            var visitor = new DefVisitor(this);
            foreach (var e  in expressions)
            {
                StdOut.WriteLine(visitor.Visit(e));
            }
        }

        //public void Def(params Func<MathValue>[] expressions)
        //{
        //    var visitor = new DefExpressionVisitor();
        //    foreach (var e in expressions)
        //    {
        //        var exp = e.ToExpression();
        //        var s = visitor.GetExpressionString(exp);
        //        StdOut.WriteLine(s);
        //    }
        //}

        public void SetStdOut(TextWriter stdOut)
        {
            StdOut = stdOut;
        }

        public void SetStdIn(TextReader stdIn)
        {
            StdIn = stdIn;
        }

        public void SetStdError(TextWriter stdError)
        {
            StdError = stdError;
        }

        #endregion

        #region Domain Methods

        /// <summary>
        /// Resets the root domain with default values and clears out all other domains from memory.  This essentially resets the program
        /// </summary>
        public void ResetRoot()
        {
            ResetConstants();

            RootDomain = new Domain("_", null,this, null, Scope.Global);
            CurrentDomain = RootDomain;

            // Load system functions
            var functions = (from m in typeof(SystemFunctions).GetMethods()
                             where  m.GetCustomAttributes(typeof(SystemFunctionAttribute), false).Length == 1
                             select m).ToArray();

            foreach (var f in functions)
            {
                var methodInfo = (SystemFunctionAttribute)f.GetCustomAttributes(typeof(SystemFunctionAttribute), false)[0];

                var paramInfo = (from a in f.GetCustomAttributes(typeof(ParameterAttribute), false)
                                select a as ParameterAttribute).OrderBy(p=>p.Ordinal).ToArray();

                var parameters =
                    paramInfo.Select(p => new FunctionParameterExpression(p.Name,(ValueExpression)InterpretText(p.DefaultValue),this));

                RootDomain.CreateFunction(methodInfo.Name, SystemFunctionWrapper(f, parameters), parameters.ToArray(), Scope.GlobalConst);
            }

            // load system varibles
            RootDomain.CreateVariable("pi", new ConstantValueExpression(PI, this), Scope.GlobalConst);
            RootDomain.CreateVariable("e", new ConstantValueExpression(E, this), Scope.GlobalConst);
            RootDomain.CreateVariable("i", new ConstantValueExpression(I, this), Scope.GlobalConst);
            RootDomain.CreateVariable("infinity", new ConstantValueExpression(Infinity, this), Scope.GlobalConst);
            RootDomain.CreateVariable("undefined", new ConstantValueExpression(Undefined, this), Scope.GlobalConst);
        }

        private void ResetConstants()
        {
            Void = new VoidValue(this);
 	        
            True = new BooleanValue(true,this);
            False = new BooleanValue(false,this);

            EmptySet = new SetValue(new ValueExpression[0],this);

            Undefined = new NumberValue(double.NaN,this);

            NegativeInfinity = NewNumber(double.NegativeInfinity);
            Infintismal = NewNumber(1.0 / double.PositiveInfinity);
            MinNumber = NewNumber(double.MinValue);
            Zero = NewNumber(0);
            One = NewNumber(1);
            E = NewNumber(Math.E);
            PI = NewNumber(Math.PI);
            MaxNumber = new NumberValue(double.MaxValue,this);
            Infinity = NewNumber(double.PositiveInfinity);

            I = new ComplexValue((NumberValue)Zero, (NumberValue)One,this);

            EmptyString = NewString(string.Empty);

            TrueString = NewString("true");
            FalseString = NewString("false");
            UndefinedString = NewString("undefined");
            NegativeInfinityString = NewString("-infinity");
            InfinityString = NewString("infinity");
        }

        internal void SetCurrentScope(BlockSymbol scope)
        {
            var domain = scope as Domain;
            if (domain!=null)
                CurrentDomain = domain;
            CurrentScope = scope;
        }

        #endregion

        #region New Value Creation methods

        /// <summary>
        /// Creates a new numeric value from the provided value
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public MathValue NewNumber(double value)
        {
            return new NumberValue(value,this);
        }

        /// <summary>
        /// Creates a new unit with the provided value and measure
        /// </summary>
        /// <param name="value"></param>
        /// <param name="measure"></param>
        /// <returns></returns>
        public MathValue NewUnit(double value, UnitMeasure measure)
        {
            return new UnitValue(value, measure,this);
        }

        /// <summary>
        /// Creates a new unit with the provided value and measure
        /// </summary>
        /// <param name="value"></param>
        /// <param name="measure"></param>
        /// <returns></returns>
        public MathValue NewUnit(NumberValue value, UnitMeasure measure)
        {
            return new UnitValue(value.Value, measure,this);
        }

        /// <summary>
        /// Creates a new complex value with the provided real and imaginary components
        /// </summary>
        /// <param name="real"></param>
        /// <param name="i"></param>
        /// <returns></returns>
        public MathValue NewComplex(double real, double i)
        {
            return new ComplexValue((NumberValue)NewNumber(real), (NumberValue)NewNumber(i),this);
        }

        /// <summary>
        /// Creates a new complex value with the provided real and imaginary components
        /// </summary>
        /// <param name="real"></param>
        /// <param name="i"></param>
        /// <returns></returns>
        public  MathValue NewComplex(NumberValue real, NumberValue i)
        {
            return new ComplexValue(real, i,this);
        }

        /// <summary>
        /// Creates a new complex value with the provided real and imaginary components
        /// </summary>
        /// <param name="real"></param>
        /// <param name="i"></param>
        /// <returns></returns>
        public  MathValue NewComplex(NumberValue real)
        {
            return new ComplexValue(real, (NumberValue)Zero,this);
        }

        /// <summary>
        /// Creates a new range with the specified upper and lower bounds
        /// </summary>
        /// <param name="lower"></param>
        /// <param name="upper"></param>
        /// <returns></returns>
        public MathValue NewRange(double lower, double upper)
        {
            return new RangeValue((NumberValue)NewNumber(lower), (NumberValue)NewNumber(upper),this);
        }

        public MathValue NewRange(NumberValue lower, NumberValue upper)
        {
            return new RangeValue(lower, upper,this);
        }

        /// <summary>
        /// Creates a new range with a span of zero
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public MathValue NewRange(double value)
        {
            return NewRange(value, value);
        }

        /// <summary>
        /// Creates a new range with a span of zero
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public MathValue NewRange(NumberValue value)
        {
            return NewRange(value, value);
        }

        public MathValue NewSet(params MathValue[] values)
        {
            return NewSet(values.Select(v => new ConstantValueExpression(v, this)));
        }

        /// <summary>
        /// Creates a new set with the specified values
        /// </summary>
        /// <param name="values"></param>
        /// <returns></returns>
        public  MathValue NewSet(IEnumerable<MathValue> values)
        {
            return NewSet(values.Select(v => new ConstantValueExpression(v, this)));
        }

        public MathValue NewSet(IEnumerable<ValueExpression> values)
        {
            return new SetValue(values,this);
        }

        /// <summary>
        /// Creates a new set with a single value
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public MathValue NewSet(MathValue item)
        {
            return new SetValue(new[]{new ConstantValueExpression(item, this)}, this);
        }

        /// <summary>
        /// Returns the boolean value true or false based on the provided data
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public MathValue NewBool(bool value)
        {
            return value ? True : False;
        }

        /// <summary>
        /// Creates a new string value from the provided data
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public MathValue NewString(string value)
        {
            if (value == null)
                throw new ArgumentNullException("value");

            return new StringValue(value,this);
        }


        #endregion

        #endregion

        #region Static Helpers

        /// <summary>
        /// Provides a wrapper for system functions to make calling them easier
        /// </summary>
        /// <param name="method"></param>
        /// <returns></returns>
        private  DelegateExpression SystemFunctionWrapper(MethodInfo method,IEnumerable<FunctionParameterExpression> parameters)
        {
            Func<MathValue> wrapper = delegate()
            {
                var vals= new List<MathValue>();
                foreach (var p in parameters)
                {
                    var variable = CurrentScope.GetVariable(p.Name);
                    vals.Add(variable.Invoke());
                }
                return (MathValue)method.Invoke(this.SystemFunctions,vals.ToArray());
            };


            return new DelegateExpression(wrapper, this);
        }

        internal static Expression EnsureObjectResult(Expression expr)
        {
            if (!expr.Type.IsValueType)
                return expr;
            if (expr.Type == typeof(void))
                return Expression.Block(expr, Expression.Default(typeof(object)));
            else
                return Expression.Convert(expr, typeof(object));
        }

        #endregion

        public static  MathRuntime DefaultRuntime = new MathRuntime();
    }
}
